/**
 * Copyright (c) 2014, Andrea Funto'. All rights reserved.
 *
 * This file is part of the Zephyr framework ("Zephyr").
 *
 * Zephyr is free software: you can redistribute it and/or modify it under 
 * the terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 *
 * Zephyr is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License 
 * along with Zephyr. If not, see <http://www.gnu.org/licenses/>.
 */

package org.dihedron.zephyr.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

import org.dihedron.commons.filters.compound.And;
import org.dihedron.commons.filters.compound.Not;
import org.dihedron.commons.reflection.Reflections;
import org.dihedron.commons.reflection.Types;
import org.dihedron.commons.reflection.filters.HasAnnotation;
import org.dihedron.commons.reflection.filters.IsOverloaded;
import org.dihedron.commons.reflection.filters.IsPublic;
import org.dihedron.commons.reflection.filters.NameIs;
import org.dihedron.commons.strings.Strings;
import org.dihedron.zephyr.annotations.Action;
import org.dihedron.zephyr.annotations.In;
import org.dihedron.zephyr.annotations.InOut;
import org.dihedron.zephyr.annotations.Invocable;
import org.dihedron.zephyr.annotations.Model;
import org.dihedron.zephyr.annotations.Out;
import org.dihedron.zephyr.exceptions.DeploymentException;
import org.dihedron.zephyr.protocol.Scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The class that creates the proxy for a given action. The proxy creation must
 * be performed all at once because Javassist applies several mechanisms such as
 * freezing and classloading that actually consolidate a class' internal status
 * and load its bytecode into the classloader, eventually making it
 * unmodifiable. This class performs all inspection of the original business
 * class in one shot and then proceeds to creating the source code for the
 * corresponding methods on the proxy class, and finally converting the
 * synthetic class into bytecode; once this process is compete, no further
 * modifications (such as method additions) can be performed and the proxy is
 * sealed for good.
 * 
 * @author Andrea Funto'
 */
public class ActionProxyBuilder {

	// TODO: proxy prefix and suffix, as well as method prefix and suffix could
	// be
	// provided by the user via a custom parameter, in order to allow easier
	// integration with other technologies that may themselves generate
	// synthetic classes thus leading to name clashing.

	/**
	 * The name of the factory method on the proxy class; this method allows to 
	 * create an instance of the original business class without resorting to
	 * reflection at runtime. 
	 */
	private static final String DEFAULT_ACTION_FACTORY_METHOD_NAME = "_makeAction";

	/**
	 * A prefix that may be prepended to the original business class name to get
	 * the name of the synthetic proxy class to be generated by the framework.
	 */
	private static final String DEFAULT_PROXY_CLASS_NAME_PREFIX = "";

	/**
	 * A suffix that may be post-pended to the original business class name to
	 * get the name of the synthetic proxy class to be generated by the
	 * framework.
	 */
	private static final String DEFAULT_PROXY_CLASS_NAME_SUFFIX = "$Proxy";

	/**
	 * A prefix that may be prepended to the original business method name to
	 * get the name of the synthetic proxy method to be generated by the
	 * framework.
	 */
	private static final String DEFAULT_PROXY_METHOD_NAME_PREFIX = "_";

	/**
	 * A suffix that may be post-pended to the original business method name to
	 * get the name of the synthetic proxy method to be generated by the
	 * framework.
	 */
	private static final String DEFAULT_PROXY_METHOD_NAME_SUFFIX = "";

	private static String actionFactoryMethodName = DEFAULT_ACTION_FACTORY_METHOD_NAME;
	private static String proxyClassNamePrefix = DEFAULT_PROXY_CLASS_NAME_PREFIX;
	private static String proxyClassNameSuffix = DEFAULT_PROXY_CLASS_NAME_SUFFIX;
	private static String stubMethodNamePrefix = DEFAULT_PROXY_METHOD_NAME_PREFIX;
	private static String stubMethodNameSuffix = DEFAULT_PROXY_METHOD_NAME_SUFFIX;

	public class ActionProxyBuilderContext {

		/**
		 * The business class around which we're building a proxy.
		 */
		private Class<?> action = null;

		/**
		 * Whether the proxied class has instance fields, which would force us
		 * to create a new instance per invocation.
		 */
		private boolean hasState;

		/**
		 * Whether JSR-349 validation code should be generated.
		 */
		private boolean doValidation;

		/**
		 * The Javassist object that actually compiles and creates the Proxy
		 * class bytecode; we call this "metaclass", since it is a representaton
		 * of the Proxy class in Javassist terms (CtMethods, CtFields etc.) and
		 * acts as an archetype for the new Java class.
		 */
		private CtClass metaclass = null;

		/**
		 * Constructor.
		 * 
		 * @param doValidation
		 *   whether the builder will emit code to support JSR-349 validation.
		 */
		ActionProxyBuilderContext(boolean doValidation) {
			this.doValidation = doValidation;
		}

		/**
		 * Specifies the user-provided class that will be proxied by the new
		 * proxy generated by this builder. This method performs some checks on
		 * the action class, to make sure that it has a no arguments constructor
		 * (this is necessary to be able to instantiate it at runtime) and
		 * whether it has instance fields (which would force us to create a new
		 * instance of the proxied class per invocation, instead of recycling
		 * the same instance across invocations). starts creating the new Proxy
		 * class by acquiring a Javassist compiler class and creating the
		 * skeleton of the class, including a logger an possibly all the fields
		 * needed to support JSR-349 validation and instance reuse.
		 * <em>NOTE: this method <b>MUST</b> be called before any {@code add*} method
		 * (that is, before adding the action factory method and the business method
		 * proxies).</em>
		 * 
		 * @return 
		 *   the object itself, for method chaining.
		 * @throws DeploymentException
		 */
		ActionProxyBuilderContext on(Class<?> action) throws DeploymentException {

			if (action == null) {
				// fluent API state machine check: when we get here we must
				// already
				// have been provided a valid action class to work on
				logger.error("this is probably a bug in your code: did you call method \"on(<action class>)\" to provide tha class to work on, before invoking this method?");
				throw new DeploymentException(
						"the action class must be specified: did you call method \"on(<action class>)\" before invoking this method?");
			}

			logger.trace("building proxy around class '{}'...", action.getName());

			// check if there is a no-args contructor
			try {
				logger.trace("checking if class '{}' has a no-args constructor...", action.getSimpleName());
				action.getConstructor();
				logger.trace("... yes, it has a no-args constructor");
			} catch (SecurityException e) {
				logger.error("error trying to access no-args constructor for class '" + action.getSimpleName() + "'", e);
				throw new DeploymentException("Error trying to access no-args constructor for class '" + action.getSimpleName() + "'", e);
			} catch (NoSuchMethodException e) {
				logger.error("class '" + action.getSimpleName()
						+ "' does not have a no-args constructor, please ensure it has one or it cannot be deployed", e);
				throw new DeploymentException("Class '" + action.getSimpleName()
						+ "' does not have a no-args constructor, please ensure it has one or it cannot be deployed", e);
			}

			// check if it has instance fields, by walking up the class
			// hierarchy
			// and gathering them as we go
			logger.trace("checking if actions of class '{}' have instance fields...", action.getSimpleName());

			this.action = action;
			this.hasState = !Reflections.getInstanceFields(action).isEmpty();

			logger.trace("... actions of class '{}' {} have instance fields", action.getSimpleName(), hasState ? "do" : "don't");

			String proxyname = getProxyClassName(action);
			try {
				logger.trace("trying to retrieve metaclass '{}' for class '{}' from class pool...", proxyname, action.getSimpleName());
				metaclass = classpool.get(proxyname);
				metaclass.defrost();
				logger.trace("... metaclass found!");
			} catch (NotFoundException e1) {
				logger.trace("... metaclass not found in class pool, creating and adding new one...");
				// use the web application class path and class loader
				classpool.insertClassPath(new ClassClassPath(action));
				metaclass = classpool.makeClass(proxyname);
				try {
					// add the SLF4J logger
					CtField log = CtField.make("private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(" + proxyname
							+ ".class);", metaclass);
					metaclass.addField(log);

					if (doValidation) {
						// add the static JSR-349 method and bean validators
						CtField validator = CtField.make("private static javax.validation.executable.ExecutableValidator methodValidator = null;",
								metaclass);
						metaclass.addField(validator);
						validator = CtField.make("private static javax.validation.Validator beanValidator = null;", metaclass);
						metaclass.addField(validator);
					}

					if (hasState) {
						logger.trace("factory method will renew action instances at each invocation");
					} else {
						logger.trace("factory method will reuse a single, cached action instance");
						// add the singleton instance; it will be used to store
						// the single instance for actions that
						// can be cached (see method comments to see when an
						// action can be cached and reused)
						CtField singleton = CtField.make(
								"private final static " + action.getCanonicalName() + " singleton = new " + action.getCanonicalName() + "();",
								metaclass);
						metaclass.addField(singleton);
					}
					logger.trace("... metaclass added");
				} catch (CannotCompileException e2) {
					logger.error("error compiling SLF4J logger expression", e2);
					throw new DeploymentException("error compiling synthetic code in dynamic proxy class creation", e2);
				}
			}
			return this;
		}

		/**
		 * Starts building the new proxy class, by generating or recovering from
		 * the Javassist {@code ClassPool} the {@code CtClass} object
		 * representing the the new proxy. The proxy class will have an SLF4J
		 * logger (as a static final field), possible a couple of fields to
		 * support JSR-349 bean and method validation, a static factory method
		 * to get an instance of the proxied class, and a set of static methods,
		 * each proxying one of the proxied class' instance methods. The static
		 * factory method (_makeProxy) is use, used to retrieve the actual
		 * user-provided business action instance used at runtime without having
		 * to resort to reflection. Depending on the structure of the
		 * user-provided business action class, the factory method will decide
		 * whether a single instance can be instantiated and reused at runtime
		 * (stateless models) or a new one must be created per invocation. In
		 * order to do this, the class will be scanned for the presence of
		 * non-static fields, and if found the action will be non-cacheable
		 * (since there will be fields on which there would be concurrent access
		 * were the same action used to service multiple requests). Thus, when
		 * creating the code for the factory method, some reflection is employed
		 * to check if there are no instance fields and only in that case the
		 * {@code singleton} field will be pre-initialised with a reference to a
		 * singleton instance of the action. If any non-static field is found,
		 * then each invocation will cause a new action instance to be created.
		 * 
		 * @param action
		 *   the action for which a proxy must be created.
		 * @return 
		 *   the object itself, for method chaining.
		 * @throws DeploymentException
		 */
		public ActionProxyBuilderContext addActionFactoryMethod() throws DeploymentException {
			String methodName = getActionFactoryName(action);
			logger.trace("action factory method for actions of class '{}' will be named '{}'", action.getSimpleName(), methodName);

			try {
				StringBuilder code = new StringBuilder("public static final ").append(action.getCanonicalName()).append(" ").append(methodName)
						.append("() {\n");
				code.append("\tlogger.trace(\"entering action factory method...\");\n");

				if (doValidation) {
					// try to initialise the JSR-349 validator
					code.append("\ttry {\n");
					code.append("\t\tif(beanValidator == null) {\n");
					code.append("\t\t\tbeanValidator = javax.validation.Validation.buildDefaultValidatorFactory().getValidator();\n");
					code.append("\t\t\tlogger.info(\"JSR-349 bean validator successfully initialised\");\n");
					code.append("\t\t} else {\n");
					code.append("\t\t\tlogger.trace(\"JSR-349 bean validator already initialised\");\n");
					code.append("\t\t}\n");
					code.append("\t\tif(methodValidator == null) {\n");
					code.append("\t\t\tmethodValidator = javax.validation.Validation.buildDefaultValidatorFactory().getValidator().forExecutables();\n");
					code.append("\t\t\tlogger.info(\"JSR-349 method validator successfully initialised\");\n");
					code.append("\t\t} else {\n");
					code.append("\t\t\tlogger.trace(\"JSR-349 method validator already initialised\");\n");
					code.append("\t\t}\n");
					code.append("\t} catch(javax.validation.ValidationException e) {\n");
					code.append("\t\tlogger.error(\"error initialising JSR-349 validators: validation will not be available throughout this session\", e);\n");
					code.append("\t}\n");
				}

				// depending on whether the class has instance fields, we decide
				// here
				// to reuse a single instance (which, being stateless, can be
				// recycled
				// across requests without fear of interference) or we should
				// allocate
				// a brand new one per request
				if (hasState) {
					code.append("\tlogger.trace(\"instantiating brand new non-cacheable object\");\n");
					code.append("\t").append(action.getCanonicalName()).append(" action = new ").append(action.getCanonicalName()).append("();\n");
				} else {
					code.append("\tlogger.trace(\"reusing single, cached instance\");\n");
					code.append("\t").append(action.getCanonicalName()).append(" action = singleton;\n");
				}
				code.append("\tlogger.trace(\"... leaving factory method\");\n");
				code.append("\treturn action;\n").append("}");
				logger.trace("compiling code:\n\n{}\n", code);

				CtMethod factoryMethod = CtNewMethod.make(code.toString(), metaclass);
				metaclass.addMethod(factoryMethod);
				return this;
			} catch (CannotCompileException e) {
				logger.error("error compiling AOP code in action factory method creation", e);
				throw new DeploymentException("error compiling AOP code in action factory method creation", e);
			}
		}

		/**
		 * Enumerates all methods in the action class and its super-classes,
		 * performs a set of checks against coding errors and bugs (e.g.
		 * annotation of static or non-public methods), then retrieves the list
		 * of annotated, public, non- overloaded methods and invokes
		 * {@link #addBusinessMethod(Method)} on each of them.
		 * 
		 * @return 
		 *   the object itself, for method chaining.
		 * @throws DeploymentException
		 */
		public ActionProxyBuilderContext addAllBusinessMethods() throws DeploymentException {

			// DIAGNOSTICS: we try to detect improper usage of annotations,
			// invalid
			// access specification (private, protected) to annotated methods,
			// overloading of annotated methods, annotation of static methods
			// etc.;
			// this is to help developers debug trivial problems in their
			// actions

			// first: check OVERLOADED annotated methods
			Set<Method> overloaded = Reflections.getInstanceMethods(action, new And<Method>(new IsOverloaded<Method>(), new HasAnnotation<Method>(
					Invocable.class)));
			if (!overloaded.isEmpty()) {
				List<String> names = new ArrayList<String>();
				for (Method method : overloaded) {
					names.add(method.getName());
					logger.error("overloading of @Invocable methods is not allowed: check that only one method named '{}' be in class '{}'",
							method.getName(), method.getDeclaringClass().getCanonicalName());
				}
				String methods = Strings.join(", ", names);
				throw new DeploymentException("overloading of @Invocable methods is not allowed: check methods " + methods + " in class '"
						+ action.getCanonicalName() + "' or super-classes");
			}

			// second: check STATIC annotated methods
			Set<Method> statics = Reflections.getClassMethods(action, new HasAnnotation<Method>(Invocable.class));
			if (!statics.isEmpty()) {
				List<String> names = new ArrayList<String>();
				for (Method method : statics) {
					names.add(method.getName());
					logger.error("@Invocable-annotated methods cannot be static: check method '{}' in class '{}'", method.getName(), method
							.getDeclaringClass().getSimpleName());
				}
				String methods = Strings.join(", ", names);
				throw new DeploymentException("annotation with @Invocable of static methods is not allowed: check methods " + methods + " in class '"
						+ action.getCanonicalName() + "' or super-classes");
			}

			// third: check NON-PUBLIC annotated methods
			Set<Method> unaccessible = Reflections.getInstanceMethods(action, new And<Method>(new HasAnnotation<Method>(Invocable.class),
					new Not<Method>(new IsPublic<Method>())));
			if (!unaccessible.isEmpty()) {
				List<String> names = new ArrayList<String>();
				for (Method method : unaccessible) {
					names.add(method.getName());
					logger.error("@Invocable-annotated methods must be public: check method '{}' in class '{}'", method.getName(), method
							.getDeclaringClass().getSimpleName());
				}
				String methods = Strings.join(", ", names);
				throw new DeploymentException("annotation with @Invocable of non-public methods is not allowed: check methods " + methods
						+ " in class '" + action.getCanonicalName() + "' or super-classes");
			}

			// if we're here, then there were no bugs in the code, proceed with
			// instrumentation of annotated public non-overloaded instance
			// methods
			Set<Method> valid = Reflections.getInstanceMethods(action, new And<Method>(new HasAnnotation<Method>(Invocable.class),
					new IsPublic<Method>(), new Not<Method>(new IsOverloaded<Method>())));
			for (Method method : valid) {
				logger.info("generating proxy for valid method '{}' from class '{}'", method.getName(), method.getDeclaringClass().getSimpleName());
				addBusinessMethod(method);
			}

			return this;
		}

		/**
		 * Creates the Java code to proxy an action method. The code will also
		 * provide parameter injection (for {@code @In}-, {@code @InOut}- and
		 * {@code @Model}- annotated parameters), JSR-349 based validation,
		 * result extraction (for {@code @Out} and {@code @InOut}-annotated
		 * parameters, and basic profiling to measure how long it takes for the
		 * business method to execute. Each stub method is actually static, so
		 * there is no need to have an instance of the proxy class to invoke it
		 * and there's no possibility that any state is kept between
		 * invocations.
		 * 
		 * @param method
		 *   the specific business action method to stub.
		 * @return 
		 *   the object itself, for method chaining.
		 * @throws DeploymentException
		 */
		public ActionProxyBuilderContext addBusinessMethod(Method method) throws DeploymentException {

			// check that the @Invocable method has the String return type,
			// otherwise
			// there would be no navigation info available at runtime (and a
			// "stack
			// off by one exception" if the return type is void.
			if (method.getReturnType() != String.class) {
				logger.error("method '{}' of action '{}' (declared in class '{}') does not have the 'String' return type", method.getName(),
						action.getCanonicalName(), method.getDeclaringClass().getSimpleName());
				throw new DeploymentException("method '" + method.getName() + "' in action '" + action.getCanonicalName()
						+ "' does not have the String return type");
			}

			String stubMethodName = makeStubMethodName(method);
			String actionAlias = getActionAlias();
			logger.trace("method '{}' (action alias '{}') will be proxied by '{}'", method.getName(), actionAlias, stubMethodName);
			try {

				StringBuilder code = new StringBuilder("public static final java.lang.String ").append(stubMethodName).append("( java.lang.Object action ) {\n\n");

				code.append("\tlogger.trace(\"entering proxy method...\");\n");
				code.append("\tjava.lang.StringBuilder trace = new java.lang.StringBuilder();\n");
				code.append("\tjava.lang.Object value = null;\n");
				if (doValidation) {
					code.append("\tjava.lang.reflect.Method methodToValidate = null;\n");
					code.append("\tjava.util.List validationValues = null;\n");
					code.append("\torg.dihedron.zephyr.validation.ValidationHandler handler = null;\n");
				}
				code.append("\n");

				Annotation[][] annotations = method.getParameterAnnotations();
				Type[] types = method.getGenericParameterTypes();

				if (doValidation) {
					code.append("\t//\n\t// JSR-349 validation code\n\t//\n");

					code.append("\tif(methodValidator != null) {\n");
					code.append("\t\tvalidationValues = new java.util.ArrayList();\n");
					code.append("\t\tmethodToValidate = $1.getClass().getMethod(\"").append(method.getName()).append("\"");

					if (types.length > 0) {
						code.append(", new Class[] {\n");
						boolean first = true;
						for (Type type : types) {
							code.append(first ? "\t\t\t" : ",\n\t\t\t");
							if (Types.isSimple(type)) {
								code.append(Types.getAsString(type)).append(".class");
							} else if (Types.isGeneric(type)) {
								code.append(Types.getAsRawType(type)).append(".class");
							}
							first = false;
						}
						code.append("\n\t\t}");
					} else {
						code.append(", null");
					}
					code.append(");\n");
					code.append("\t} else {\n");
					code.append("\t\tlogger.trace(\"no JSR-349 method validation available\");\n");
					code.append("\t}\n\n");
				}

				// now get the values for each parameter, including those to
				// validate
				StringBuilder args = new StringBuilder();
				StringBuilder validCode = new StringBuilder();
				StringBuilder preCode = new StringBuilder();
				StringBuilder postCode = new StringBuilder();
				for (int i = 0; i < types.length; ++i) {
					if (doValidation) {
						validCode.append("\t\t\t");
					}
					String arg = prepareArgument(i, types[i], annotations[i], actionAlias, method, preCode, postCode, doValidation);
					args.append(args.length() > 0 ? ", " : "").append(arg);
				}

				code.append(preCode);

				code.append("\tif(trace.length() > 0) {\n\t\ttrace.setLength(trace.length() - 2);\n\t\tlogger.debug(trace.toString());\n\t}\n\n");

				// if validation should occur, and there are both a valid
				// JSR-349 validator and
				// a valid set of information (method and arguments), then the
				// validator will
				// be invoked and its results passed on either to the registered
				// validation
				// handler or to the default one (which does nothing but print
				// out a message)
				if (doValidation) {
					code.append("\t//\n\t// JSR-349 parameters validation\n\t//\n");
					code.append("\tif(methodToValidate != null) {\n");
					code.append("\t\tlogger.trace(\"validating invocation parameters\");\n");
					code.append("\t\tObject[] array = validationValues.toArray(new java.lang.Object[validationValues.size()]);\n");
					code.append("\t\tjava.util.Set violations = methodValidator.validateParameters((").append(action.getCanonicalName()).append(")$1, methodToValidate, array, new java.lang.Class[] { javax.validation.groups.Default.class });\n");

					code.append("\t\tif(violations.size() > 0) {\n");
					code.append("\t\t\tlogger.warn(\"{} constraint violations detected in input parameters\", new java.lang.Object[] { new java.lang.Integer(violations.size()) });\n");

					// now grab the ValidationHandler
					Invocable invocable = (Invocable) method.getAnnotation(Invocable.class);
					code.append("\t\t\tif(handler == null) {\n");
					code.append("\t\t\t\thandler = new ").append(invocable.validator().getCanonicalName()).append("();\n");
					code.append("\t\t\t}\n");
					code.append("\t\t\tjava.lang.String result = handler.onParametersViolations(").append("\"").append(actionAlias).append("\", \"").append(method.getName()).append("\", violations);\n");
					code.append("\t\t\tif(result != null) {\n");
					code.append("\t\t\t\tlogger.debug(\"violation handler forced return value to be '{}'\", result);\n");
					code.append("\t\t\t\treturn result;\n");
					code.append("\t\t\t}\n");
					code.append("\t\t}\n");

					code.append("\t} else if(methodValidator != null) {\n");
					code.append("\t\tlogger.warn(\"method is null\");\n");
					code.append("\t}\n");

					code.append("\n");
				}

				code.append("\t//\n\t// invoking proxied method\n\t//\n");
				code.append("\tlong millis = java.lang.System.currentTimeMillis();\n");
				code.append("\tjava.lang.String result = ((").append(action.getCanonicalName()).append(")$1).").append(method.getName()).append("(").append(args).append(");\n");

				code.append("\n");

				if (doValidation) {
					// now apply JSR-349 validation to result
					code.append("\t//\n\t// JSR-349 result validation\n\t//\n");
					code.append("\tif(methodToValidate != null) {\n");
					code.append("\t\tlogger.trace(\"validating invocation results\");\n");
					code.append("\t\tjava.util.Set violations = methodValidator.validateReturnValue((").append(action.getCanonicalName()).append(")$1, methodToValidate, result, new java.lang.Class[] { javax.validation.groups.Default.class });\n");

					code.append("\t\tif(violations.size() > 0) {\n");
					code.append("\t\t\tlogger.debug(\"{} constraint violations detected in result\", new java.lang.Object[] { new java.lang.Integer(violations.size()) });\n");

					// now grab the ValidationHandler
					Invocable invocable = (Invocable) method.getAnnotation(Invocable.class);
					code.append("\t\t\tif(handler == null) {\n");
					code.append("\t\t\t\thandler = new ").append(invocable.validator().getCanonicalName()).append("();\n");
					code.append("\t\t\t}\n");
					code.append("\t\t\tjava.lang.String forcedResult = handler.onResultViolations(").append("\"").append(actionAlias).append("\", \"").append(method.getName()).append("\", violations);\n");
					code.append("\t\t\tif(forcedResult != null) {\n");
					code.append("\t\t\t\tlogger.debug(\"violation handler forced return value to be '{}'\", forcedResult);\n");
					code.append("\t\t\t\tresult = forcedResult;\n");
					code.append("\t\t\t}\n");
					code.append("\t\t}\n");
					code.append("\t}\n");

					code.append("\n");
				}

				// code executed after the action has been fired, e.g. storing
				// [in]out parameters into scopes
				if (postCode.length() > 0) {
					code.append("\t//\n\t// post action execution: store @Out parameters into scopes\n\t//\n\n");
					code.append(postCode);
				}

				code.append("\tlogger.debug(\"result is '{}' (execution took {} ms)\", result, new java.lang.Long((java.lang.System.currentTimeMillis() - millis)).toString());\n");
				code.append("\tlogger.trace(\"... leaving proxy method\");\n");
				code.append("\treturn result;\n");

				code.append("}");

				logger.trace("compiling code:\n\n{}\n", code);

				CtMethod stubMethod = CtNewMethod.make(code.toString(), metaclass);
				metaclass.addMethod(stubMethod);
				return this;

			} catch (CannotCompileException e) {
				logger.error("error compiling AOP code in method creation", e);
				throw new DeploymentException("error compiling AOP code in method creation", e);
			} catch (SecurityException e) {
				logger.error("security violation getting declared method '" + stubMethodName + "'", e);
				throw new DeploymentException("security violation getting declared method '" + stubMethodName + "'", e);
			}
		}

		public ActionProxy getActionProxy() throws DeploymentException {

			ActionProxy proxy = new ActionProxy();

			try {
				// seal, compile and load the proxy class into the classloader
				logger.info("sealing and loading the proxy class in the original action's classloader");
				Class<?> proxyClass = metaclass.toClass(action.getClassLoader(), null);
				proxy.setProxyClass(proxyClass);

				// loop over original methods, get a reference to its proxy and
				// store
				// both ins an original-to-proxy map
				Map<Method, Method> mapping = new HashMap<Method, Method>();
				Set<Method> originals = Reflections.getInstanceMethods(action, new And<Method>(new HasAnnotation<Method>(Invocable.class),
						new IsPublic<Method>(), new Not<Method>(new IsOverloaded<Method>())));
				for (Method original : originals) {
					String name = makeStubMethodName(original);
					Set<Method> proxies = Reflections.getClassMethods(proxyClass, new NameIs<Method>(name));
					if (proxies.isEmpty() || proxies.size() != 1) {
						logger.error("unexpected: there is more than one proxy method '{}' for original method '{}' in class '{}'", name,
								original.getName(), action.getSimpleName());
						throw new DeploymentException("there is more than one proxy method '" + name + "' for original method '" + original.getName()
								+ "' in class '" + action.getSimpleName() + "'");
					}
					Method proxying = proxies.iterator().next();
					logger.trace("original method '{}' in class '{}' will be proxied by '{}' in class '{}'", original.getName(), original
							.getDeclaringClass().getSimpleName(), proxying.getName(), proxying.getDeclaringClass().getSimpleName());
					mapping.put(original, proxying);
				}
				proxy.setStubMethods(mapping);

				// next add the reference to the action factory (constructor)
				// method
				Method factory = proxyClass.getDeclaredMethod(getActionFactoryName(action));
				proxy.setActionFactory(factory);
			} catch (CannotCompileException e) {
				logger.error("error sealing the proxy class for '{}'", action.getSimpleName());
				throw new DeploymentException("error sealing proxy class for action '" + action.getSimpleName() + "'", e);
			} catch (SecurityException e) {
				logger.error("error accessing the factory method for class '{}'", action.getSimpleName());
				throw new DeploymentException("error accessing the factory method for class '" + action.getSimpleName() + "'", e);
			} catch (NoSuchMethodException e) {
				logger.error("factory method for class '{}' not found", action.getSimpleName());
				throw new DeploymentException("factory method for class '" + action.getSimpleName() + "' not found", e);
			}
			return proxy;
		}

		private String prepareArgument(int i, Type type, Annotation[] annotations, String action, Method method, StringBuilder preCode,
				StringBuilder postCode, boolean doValidation) throws DeploymentException {
			In in = null;
			Out out = null;
			InOut inout = null;
			Model model = null;
			for (Annotation annotation : annotations) {
				if (annotation instanceof In) {
					in = (In) annotation;
				} else if (annotation instanceof Out) {
					out = (Out) annotation;
				} else if (annotation instanceof InOut) {
					inout = (InOut) annotation;
				} else if (annotation instanceof Model) {
					model = (Model) annotation;
				}
			}

			if (inout != null) {
				logger.trace("preparing input argument...");
				// safety check: verify that no @In or @Out parameters are
				// specified
				if (in != null) {
					logger.error("attention! parameter {} is annotated with incompatible annotations @InOut and @In", i);
					throw new DeploymentException("parameter " + i + " is annotated with incompatible annotations @InOut and @In");
				}
				if (out != null) {
					logger.error("attention! parameter {} is annotated with incompatible annotations @InOut and @Out", i);
					throw new DeploymentException("parameter " + i + " is annotated with incompatible annotations @InOut and @Out");
				}
				if (model != null) {
					logger.error("attention! parameter {} is annotated with incompatible annotations @InOut and @Model", i);
					throw new DeploymentException("parameter " + i + " is annotated with incompatible annotations @InOut and @Model");
				}
				return prepareInOutArgument(i, type, inout, preCode, postCode, doValidation);
			} else if (in != null && out != null) {
				if (model != null) {
					logger.error("attention! parameter {} is annotated with incompatible annotations @In/@Out and @Model", i);
					throw new DeploymentException("parameter " + i + " is annotated with incompatible annotations @In&/@Out and @Model");
				}
				logger.trace("preparing input/output argument...");
				return prepareInputOutputArgument(i, type, in, out, preCode, postCode, doValidation);
			} else if (in != null && out == null) {
				if (model != null) {
					logger.error("attention! parameter {} is annotated with incompatible annotations @In and @Model", i);
					throw new DeploymentException("parameter " + i + " is annotated with incompatible annotations @In and @Model");
				}
				logger.trace("preparing input argument...");
				return prepareInputArgument(i, type, in, preCode, doValidation);
			} else if (in == null && out != null) {
				if (model != null) {
					// prepare model/out
					logger.trace("preparing model/output argument...");
					return prepareInputOutputModelArgument(i, type, model, out, action, method, preCode, postCode, doValidation);
				} else {
					logger.trace("preparing output argument...");
					return prepareOutputArgument(i, type, out, preCode, postCode, doValidation);
				}
			} else {
				if (model != null) {
					logger.trace("preparing model argument...");
					return prepareInputModelArgument(i, type, model, action, method, preCode, doValidation);
				} else {
					logger.trace("preparing non-annotated argument...");
					return prepareNonAnnotatedArgument(i, (Class<?>) type, preCode, doValidation);
				}
			}
		}

		private String prepareInputArgument(int i, Type type, In in, StringBuilder preCode, boolean doValidation) throws DeploymentException {

			if (Types.isSimple(type) && ((Class<?>) type).isPrimitive()) {
				logger.error("primitive types are not supported on annotated parameters (check parameter '{}', no. {}, type is '{}')", in.value(), i,
						Types.getAsString(type));
				throw new DeploymentException("Primitive types are not supported as @In parameters: check parameter '" + in.value() + "' ( no. " + i
						+ ", type is '" + Types.getAsString(type) + "')");
			}

			if (Types.isGeneric(type) && Types.isOfClass(type, $.class)) {
				logger.error("input parameters must not be wrapped in typed reference holders ($) (check parameter no. {})", i);
				throw new DeploymentException("Input parameters must not be wrapped in typed reference holders ($): check parameter no. " + i);
			}

			if (!Strings.isValid(in.value())) {
				logger.error(
						"input parameters' storage name must be explicitly specified through the @In annotation's value (check parameter {}: @In's value is '{}')",
						i, in.value());
				throw new DeploymentException(
						"Input parameters's storage name must be explicitly specified through the @In annotation's value: check parameter no. " + i
								+ " (@In's value is '" + in.value() + "')");
			}

			String parameter = in.value();
			String variable = "in_" + i;

			preCode.append("\t//\n\t// preparing input argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(type)).append(")\n\t//\n");

			logger.trace("{}-th parameter is annotated with @In('{}')", i, in.value());
			preCode.append("\tvalue = org.dihedron.zephyr.ActionContext.findValue(\"").append(parameter)
					.append("\", new org.dihedron.zephyr.protocol.Scope[] {");
			boolean first = true;
			for (Scope scope : in.from()) {
				preCode.append(first ? "" : ", ").append("org.dihedron.zephyr.protocol.Scope.").append(scope.name());
				first = false;
			}
			preCode.append(" });\n");

			if (Types.isSimple(type) && !((Class<?>) type).isArray()) {
				// if parameter is not an array, pick the first element
				preCode.append("\tif(value != null && value.getClass().isArray() && ((Object[])value).length > 0) {\n\t\tvalue = ((Object[])value)[0];\n\t}\n");
			}

			preCode.append("\t").append(Types.getAsRawType(type)).append(" ").append(variable).append(" = (").append(Types.getAsRawType(type))
					.append(") value;\n");
			preCode.append("\ttrace.append(\"").append(variable).append("\").append(\" => '\").append(").append(variable)
					.append(").append(\"', \");\n");

			//
			// the value used for JSR-349 parameters validation
			//
			if (doValidation) {
				preCode.append("\n");
				preCode.append("\t// in parameter\n");
				preCode.append("\tif(validationValues != null) validationValues.add(value);\n");
			}

			preCode.append("\n");
			return variable;
		}

		private String prepareOutputArgument(int i, Type type, Out out, StringBuilder preCode, StringBuilder postCode, boolean doValidation)
				throws DeploymentException {

			if (!Types.isGeneric(type)) {
				logger.error("output parameters must be generic, and of reference type $<?> (check parameter no. {}: type is '{}'", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output parameters must generic, and of reference type $<?> (check parameter no. " + i + ": type is '"
						+ ((Class<?>) type).getCanonicalName() + " '");
			}

			if (!Types.isOfClass(type, $.class)) {
				logger.error("output parameters must be wrapped in typed reference holders ($) (check parameter {}: type is '{}')", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output parameters must be wrapped in typed reference holders ($): check parameter no. " + i
						+ " (type is '" + ((Class<?>) type).getCanonicalName() + "')");
			}

			if (out.value().trim().length() == 0) {
				logger.error(
						"output parameters' storage name must be explicitly specified through the @Out annotation's value (check parameter {}: @Out's value is '{}')",
						i, out.value());
				throw new DeploymentException(
						"Output parameters's name must be explicitly specified through the @Out annotation's value: check parameter no. " + i
								+ " (@Out value is '" + out.value() + "')");
			}

			String parameter = out.value();
			String variable = "out_" + i;

			Type wrapped = Types.getParameterTypes(type)[0];
			logger.trace("output parameter '{}' (no. {}) is of type $<{}>", parameter, i, Types.getAsString(wrapped));

			//
			// code executed BEFORE the action fires, to prepare output
			// parameters
			//
			preCode.append("\t//\n\t// preparing output argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(")\n\t//\n");

			logger.trace("{}-th parameter is annotated with @Out('{}')", i, out.value());
			// NOTE: no support for generics in Javassist: drop types (which
			// would be dropped by type erasure anyway...)
			preCode.append("\torg.dihedron.zephyr.aop.$ ").append(variable).append(" = new org.dihedron.zephyr.aop.$();\n");

			//
			// the value used for JSR-349 parameters validation
			//
			if (doValidation) {
				preCode.append("\n");
				preCode.append("\t// out parameter\n");
				preCode.append("\tif(validationValues != null) validationValues.add(null);\n");
			}

			preCode.append("\n");

			//
			// code executed AFTER the action has returned, to store values into
			// scopes
			//
			Scope scope = out.to();
			postCode.append("\t//\n\t// storing output argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(") into scope ").append(scope.name()).append("\n\t//\n");
			postCode.append("\tvalue = ").append(variable).append(".get();\n");
			postCode.append("\tif(value != null) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.setValue(\"").append(out.value())
					.append("\", value, org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(");\n");
			postCode.append("\t} else if(").append(variable).append(".isReset()) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.removeValue(\"").append(parameter).append("\", ")
					.append("org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(");\n");
			postCode.append("\t}\n");
			postCode.append("\n");

			return variable;
		}

		private String prepareInputOutputArgument(int i, Type type, In in, Out out, StringBuilder preCode, StringBuilder postCode,
				boolean doValidation) throws DeploymentException {

			if (!Types.isGeneric(type)) {
				logger.error("output parameters must be generic, and of reference type $<?> (check parameter no. {}: type is '{}'", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output parameters must generic, and of reference type $<?> (check parameter no. " + i + ": type is '"
						+ ((Class<?>) type).getCanonicalName() + " '");
			}

			if (!Types.isOfClass(type, $.class)) {
				logger.error("output parameters must be wrapped in typed reference holders ($) (check parameter {}: type is '{}')", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output parameters must be wrapped in typed reference holders ($): check parameter no. " + i
						+ " (type is '" + ((Class<?>) type).getCanonicalName() + "')");
			}

			if (in.value().trim().length() == 0) {
				logger.error(
						"input parameters' storage name must be explicitly specified through the @In annotation's value (check parameter {}: @In's value is '{}')",
						i, in.value());
				throw new DeploymentException(
						"Input parameters's storage name must be explicitly specified through the @In annotation's value: check parameter no. " + i
								+ " (@In's value is '" + in.value() + "')");
			}

			if (out.value().trim().length() == 0) {
				logger.error(
						"output parameters' storage name must be explicitly specified through the @Out annotation's value (check parameter {}: @Out's value is '{}')",
						i, out.value());
				throw new DeploymentException(
						"Output parameters's name must be explicitly specified through the @Out annotation's value: check parameter no. " + i
								+ " (@Out value is '" + out.value() + "')");
			}

			String parameter = in.value();
			String variable = "inout_" + i;

			Type wrapped = Types.getParameterTypes(type)[0];
			logger.trace("input/output parameter no. {} is of type $<{}>", i, Types.getAsString(wrapped));

			//
			// code executed BEFORE the action fires, to prepare input
			// parameters
			//
			preCode.append("\t//\n\t// preparing input/output argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(")\n\t//\n");

			logger.trace("{}-th parameter is annotated with @In('{}') and @Out('{}')", i, in.value(), out.value());
			preCode.append("\tvalue = org.dihedron.zephyr.ActionContext.findValue(\"").append(parameter)
					.append("\", new org.dihedron.zephyr.protocol.Scope[] {");
			boolean first = true;
			for (Scope scope : in.from()) {
				preCode.append(first ? "" : ", ").append("org.dihedron.zephyr.protocol.Scope.").append(scope.name());
				first = false;
			}
			preCode.append(" });\n");

			if (Types.isSimple(wrapped) && !((Class<?>) wrapped).isArray()) {
				// if parameter is not an array, pick the first element
				preCode.append("\tif(value != null && value.getClass().isArray() && ((Object[])value).length > 0) {\n\t\tvalue = ((Object[])value)[0];\n\t}\n");
			}

			// NOTE: no support for generics in Javassist: drop types (which
			// would be dropped by type erasure anyway...)
			preCode.append("\torg.dihedron.zephyr.aop.$ ").append(variable).append(" = new org.dihedron.zephyr.aop.$();\n");
			preCode.append("\t").append(variable).append(".set(value);\n");
			preCode.append("\ttrace.append(\"").append(variable).append("\").append(\" => '\").append(").append(variable)
					.append(".get()).append(\"', \");\n");

			//
			// the value used for JSR-349 parameters validation
			//
			if (doValidation) {
				preCode.append("\n");
				preCode.append("\t// in+out parameter\n");
				preCode.append("\tif(validationValues != null) validationValues.add(value);\n");
			}

			preCode.append("\n");

			//
			// code executed AFTER the action has returned, to store values into
			// scopes
			//
			parameter = out.value();
			Scope scope = out.to();
			postCode.append("\t//\n\t// storing input/output argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(") into scope ").append(scope.name()).append("\n\t//\n");
			postCode.append("\tvalue = ").append(variable).append(".get();\n");
			postCode.append("\tif(value != null) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.setValue(\"").append(out.value())
					.append("\", value, org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(");\n");
			postCode.append("\t} else if(").append(variable).append(".isReset()) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.removeValue(\"").append(parameter).append("\", ")
					.append("org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(");\n");
			postCode.append("\t}\n");
			postCode.append("\n");
			return variable;
		}

		private String prepareInOutArgument(int i, Type type, InOut inout, StringBuilder preCode, StringBuilder postCode, boolean doValidation)
				throws DeploymentException {

			if (!Types.isGeneric(type)) {
				logger.error("output parameters must be generic, and of reference type $<?> (check parameter no. {}: type is '{}'", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output parameters must generic, and of reference type $<?> (check parameter no. " + i + ": type is '"
						+ ((Class<?>) type).getCanonicalName() + " '");
			}

			if (!Types.isOfClass(type, $.class)) {
				logger.error("output parameters must be wrapped in typed reference holders ($) (check parameter {}: type is '{}')", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output parameters must be wrapped in typed reference holders ($): check parameter no. " + i
						+ " (type is '" + ((Class<?>) type).getCanonicalName() + "')");
			}

			if (inout.value().trim().length() == 0) {
				logger.error(
						"input/output parameters' storage name must be explicitly specified through the @InOut annotation's value (check parameter {}: @InOut's value is '{}')",
						i, inout.value());
				throw new DeploymentException(
						"Input parameters's storage name must be explicitly specified through the @InOut annotation's value: check parameter no. "
								+ i + " (@InOut's value is '" + inout.value() + "')");
			}

			String parameter = inout.value();
			String variable = "inout_" + i;

			Type wrapped = Types.getParameterTypes(type)[0];
			logger.trace("input/output parameter no. {} is of type $<{}>", i, Types.getAsString(wrapped));

			//
			// code executed BEFORE the action fires, to prepare input
			// parameters
			//
			preCode.append("\t//\n\t// preparing input/output argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(")\n\t//\n");

			logger.trace("{}-th parameter is annotated with @InOut('{}')", i, inout.value());
			preCode.append("\tvalue = org.dihedron.zephyr.ActionContext.findValue(\"").append(parameter)
					.append("\", new org.dihedron.zephyr.protocol.Scope[] {");
			boolean first = true;
			for (Scope scope : inout.from()) {
				preCode.append(first ? "" : ", ").append("org.dihedron.zephyr.protocol.Scope.").append(scope);
				first = false;
			}
			preCode.append(" });\n");

			if (Types.isSimple(wrapped) && !((Class<?>) wrapped).isArray()) {
				// if parameter is not an array, pick the first element
				preCode.append("\tif(value != null && value.getClass().isArray() && ((Object[])value).length > 0) {\n\t\tvalue = ((Object[])value)[0];\n\t}\n");
			}

			// NOTE: no support for generics in Javassist: drop types (which
			// would be dropped by type erasure anyway...)
			preCode.append("\torg.dihedron.zephyr.aop.$ ").append(variable).append(" = new org.dihedron.zephyr.aop.$();\n");
			preCode.append("\t").append(variable).append(".set(value);\n");
			preCode.append("\ttrace.append(\"").append(variable).append("\").append(\" => '\").append(").append(variable)
					.append(".get()).append(\"', \");\n");

			//
			// the value used for JSR-349 parameters validation
			//
			if (doValidation) {
				preCode.append("\n");
				preCode.append("\t// inout parameter\n");
				preCode.append("\tif(validationValues != null) validationValues.add(value);\n");
			}

			preCode.append("\n");

			//
			// code executed AFTER the action has returned, to store values into
			// scopes
			//
			postCode.append("\t//\n\t// storing input/output argument ").append(parameter).append(" (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(") into scope ").append(inout.to().name()).append("\n\t//\n");
			postCode.append("\tvalue = ").append(variable).append(".get();\n");
			postCode.append("\tif(value != null) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.setValue(\"").append(inout.value())
					.append("\", value, org.dihedron.zephyr.protocol.Scope.").append(inout.to().name()).append(");\n");
			postCode.append("\t} else if(").append(variable).append(".isReset()) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.removeValue(\"").append(parameter).append("\", ")
					.append("org.dihedron.zephyr.protocol.Scope.").append(inout.to().name()).append(");\n");
			postCode.append("\t}\n");
			postCode.append("\n");

			return variable;
		}

		private String prepareInputModelArgument(int i, Type type, Model model, String action, Method method, StringBuilder preCode,
				boolean doValidation) throws DeploymentException {

			if (Types.isSimple(type) && ((Class<?>) type).isPrimitive()) {
				logger.error("primitive types are not supported on annotated parameters (check parameter '{}', no. {}, type is '{}')", model.value(),
						i, Types.getAsString(type));
				throw new DeploymentException("Primitive types are not supported as @In parameters: check parameter '" + model.value() + "' ( no. "
						+ i + ", type is '" + Types.getAsString(type) + "')");
			}

			if (Types.isGeneric(type) && Types.isOfClass(type, $.class)) {
				logger.error("input-only model parameters must not be wrapped in typed reference holders ($) (check parameter no. {})", i);
				throw new DeploymentException("Input-only model parameters must not be wrapped in typed reference holders ($): check parameter no. "
						+ i);
			}

			if (!Strings.isValid(model.value())) {
				logger.error(
						"model's parameters' pattern must be explicitly specified through the @Model annotation's value (check parameter {}: @Model's pattern is '{}')",
						i, model.value());
				throw new DeploymentException(
						"Model's parameters pattern must be explicitly specified through the @Model annotation's value: check parameter no. " + i
								+ " (@Model's pattern is '" + model.value() + "')");
			}

			// double back-slashes become single in code generation!
			String pattern = model.value().replaceAll("\\\\", "\\\\\\\\");
			String variable = "model_" + i;

			preCode.append("\t//\n\t// preparing input-only model argument with pattern '").append(pattern).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(type)).append(")\n\t//\n");

			// retrieve the applicable parameters from the specified scopes
			logger.trace("{}-th parameter is annotated with @Model('{}')", i, pattern);
			preCode.append("\tjava.util.Map map = org.dihedron.zephyr.ActionContext.matchValues(\"").append(pattern)
					.append("\", new org.dihedron.zephyr.protocol.Scope[] {");
			boolean first = true;
			for (Scope scope : model.from()) {
				preCode.append(first ? "" : ", ").append("org.dihedron.zephyr.protocol.Scope.").append(scope.name());
				first = false;
			}
			preCode.append(" });\n\n");

			// TODO: instantiate an object of the given type before
			// starting setting its values with OGNL!!!

			// instantiate a new instance of the model object
			preCode.append("\t//\n\t// creating new model object instance\n\t//\n");
			preCode.append("\t").append(Types.getAsRawType(type)).append(" ").append(variable).append(" = new ").append(Types.getAsRawType(type))
					.append("();\n");
			preCode.append("\tognl.OgnlContext context = new ognl.OgnlContext();\n");

			preCode.append("\tjava.util.Iterator entries = map.entrySet().iterator();\n");

			// now loop on the available parameters, remove the mask (if
			// necessary), and inject them into the model
			preCode.append("\torg.dihedron.commons.regex.Regex regex = new org.dihedron.commons.regex.Regex(\"").append(pattern).append("\");\n");
			preCode.append("\twhile(entries.hasNext()) {\n");
			preCode.append("\t\tjava.util.Map.Entry entry = (java.util.Map.Entry)entries.next();\n");
			preCode.append("\t\tjava.lang.String key = (java.lang.String)entry.getKey();\n");

			// get the contents of the capturing group from the regular
			// expression
			preCode.append("\t\tif(regex.matches(key)) {\n");
			preCode.append("\t\t\tString[] matches = (String[])regex.getAllMatches(key).get(0);\n");
			preCode.append("\t\t\tkey = matches[0];\n");
			preCode.append("\t\t\tlogger.trace(\"key after masking out is '{}'\", key);\n");
			// create an OGNL interpreter and launch it against the model object
			preCode.append("\t\t\t// create the OGNL expression\n");
			preCode.append("\t\t\torg.dihedron.zephyr.ognl.OgnlExpression ognl = new org.dihedron.zephyr.ognl.OgnlExpression(key);\n");
			preCode.append("\t\t\tognl.setValue(context, ").append(variable).append(", entry.getValue());\n");
			preCode.append("\t\t}\n");

			// end of loop on values
			preCode.append("\t}\n\n");

			// now perform bean validation, if available
			preCode.append("\tif(beanValidator != null) {\n");
			preCode.append("\t\t// JSR-349 (or JSR-303) bean validation code\n");

			preCode.append("\t\tjava.util.Set violations = beanValidator.validate(").append(variable)
					.append(", new java.lang.Class[] { javax.validation.groups.Default.class });\n");

			preCode.append("\t\tif(violations.size() > 0) {\n");
			preCode.append("\t\t\tlogger.warn(\"{} constraint violations detected in input model\", new java.lang.Object[] { new java.lang.Integer(violations.size()) });\n");

			// now grab the ValidationHandler
			Invocable invocable = (Invocable) method.getAnnotation(Invocable.class);
			preCode.append("\t\t\tif(handler == null) {\n");
			preCode.append("\t\t\t\thandler = new ").append(invocable.validator().getCanonicalName()).append("();\n");
			preCode.append("\t\t\t}\n");
			preCode.append("\t\t\tjava.lang.String result = handler.onModelViolations(").append("\"").append(action).append("\", \"")
					.append(method.getName()).append("\", ").append(i).append(", ").append(Types.getAsRawType(type)).append(".class, violations);\n");
			preCode.append("\t\t\tif(result != null) {\n");
			preCode.append("\t\t\t\tlogger.debug(\"violation handler forced return value to be '{}'\", result);\n");
			preCode.append("\t\t\t\treturn result;\n");
			preCode.append("\t\t\t}\n");
			preCode.append("\t\t}\n");
			preCode.append("\t}\n\n");

			preCode.append("\ttrace.append(\"").append(variable).append("\").append(\" => '\").append(").append(variable)
					.append(").append(\"', \");\n");

			preCode.append("\n");
			return variable;
		}

		private String prepareInputOutputModelArgument(int i, Type type, Model model, Out out, String action, Method method, StringBuilder preCode,
				StringBuilder postCode, boolean doValidation) throws DeploymentException {

			//
			// TODO: implement from here!!!!!
			//
			if (!Types.isGeneric(type)) {
				logger.error("output model parameters must be generic, and of reference type $<?> (check parameter no. {}: type is '{}'", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output model parameters must generic, and of reference type $<?> (check parameter no. " + i
						+ ": type is '" + ((Class<?>) type).getCanonicalName() + " '");
			}

			if (!Types.isOfClass(type, $.class)) {
				logger.error("output model parameters must be wrapped in typed reference holders ($) (check parameter {}: type is '{}')", i,
						((Class<?>) type).getCanonicalName());
				throw new DeploymentException("Output model parameters must be wrapped in typed reference holders ($): check parameter no. " + i
						+ " (type is '" + ((Class<?>) type).getCanonicalName() + "')");
			}

			if (!Strings.isValid(model.value())) {
				logger.error(
						"model's parameters' pattern must be explicitly specified through the @Model annotation's value (check parameter {}: @Model's pattern is '{}')",
						i, model.value());
				throw new DeploymentException(
						"Model's parameters pattern must be explicitly specified through the @Model annotation's value: check parameter no. " + i
								+ " (@Model's pattern is '" + model.value() + "')");
			}

			if (out.value().trim().length() == 0) {
				logger.error(
						"output model parameters' storage name must be explicitly specified through the @Out annotation's value (check parameter {}: @Out's value is '{}')",
						i, out.value());
				throw new DeploymentException(
						"Output model parameters's name must be explicitly specified through the @Out annotation's value: check parameter no. " + i
								+ " (@Out value is '" + out.value() + "')");
			}

			// double back-slashes become single in code generation!
			String pattern = model.value().replaceAll("\\\\", "\\\\\\\\");

			Type wrapped = Types.getParameterTypes(type)[0];
			String variable = "model_" + i;

			preCode.append("\t//\n\t// preparing input-output model argument with pattern '").append(pattern).append("' (no. ").append(i)
					.append(", ").append(Types.getAsString(wrapped)).append(")\n\t//\n");

			// retrieve the applicable parameters from the specified scopes
			logger.trace("{}-th parameter is annotated with @Model('{}')", i, pattern);
			preCode.append("\tjava.util.Map map = org.dihedron.zephyr.ActionContext.matchValues(\"").append(pattern)
					.append("\", new org.dihedron.zephyr.protocol.Scope[] {");
			boolean first = true;
			for (Scope scope : model.from()) {
				preCode.append(first ? "" : ", ").append("org.dihedron.zephyr.protocol.Scope.").append(scope.name());
				first = false;
			}
			preCode.append(" });\n\n");

			// TODO: instantiate an object of the given type before
			// starting setting its values with OGNL!!!

			// instantiate a new instance of the model object and store it in a
			// $<?> reference
			preCode.append("\t//\n\t// creating new model object instance (which will be stored in a $<?> reference)\n\t//\n");
			preCode.append("\torg.dihedron.zephyr.aop.$ ").append(variable).append(" = new org.dihedron.zephyr.aop.$(new ")
					.append(Types.getAsString(wrapped)).append("());\n");
			preCode.append("\tognl.OgnlContext context = new ognl.OgnlContext();\n");

			preCode.append("\tjava.util.Iterator entries = map.entrySet().iterator();\n");

			// now loop on the available parameters, remove the mask (if
			// necessary), and inject them into the model
			preCode.append("\torg.dihedron.commons.regex.Regex regex = new org.dihedron.commons.regex.Regex(\"").append(pattern).append("\");\n");
			preCode.append("\twhile(entries.hasNext()) {\n");
			preCode.append("\t\tjava.util.Map.Entry entry = (java.util.Map.Entry)entries.next();\n");
			preCode.append("\t\tjava.lang.String key = (java.lang.String)entry.getKey();\n");

			// get the contents of the capturing group from the regular
			// expression
			preCode.append("\t\tif(regex.matches(key)) {\n");
			preCode.append("\t\t\tString[] matches = (String[])regex.getAllMatches(key).get(0);\n");
			preCode.append("\t\t\tkey = matches[0];\n");
			preCode.append("\t\t\tlogger.trace(\"key after masking out is '{}'\", key);\n");

			// // if there is a mask, remove it from the key name
			// preCode.append("\t\tif(org.dihedron.commons.utils.Strings.isValid(\"").append(mask).append("\")) {\n");
			// preCode.append("\t\t\t// remove the mask if specified\n");
			// preCode.append("\t\t\tkey = key.replaceFirst(\"").append(mask).append("\", \"\");\n");
			// preCode.append("\t\t\tlogger.trace(\"key after masking out is '{}'\", key);\n");
			// preCode.append("\t\t}\n");

			// create an OGNL interpreter and launch it against the model object
			preCode.append("\t\t\t// create the OGNL expression\n");
			preCode.append("\t\t\torg.dihedron.zephyr.ognl.OgnlExpression ognl = new org.dihedron.zephyr.ognl.OgnlExpression(key);\n");
			preCode.append("\t\t\tognl.setValue(context, ").append(variable).append(".get(), entry.getValue());\n");

			preCode.append("\t\t}\n");

			// end of loop on values
			preCode.append("\t}\n\n");

			// now perform bean validation, if available
			preCode.append("\tif(beanValidator != null) {\n");
			preCode.append("\t\t// JSR-349 (or JSR-303) bean validation code\n");

			preCode.append("\t\tjava.util.Set violations = beanValidator.validate(").append(variable)
					.append(".get(), new java.lang.Class[] { javax.validation.groups.Default.class });\n");

			preCode.append("\t\tif(violations.size() > 0) {\n");
			preCode.append("\t\t\tlogger.warn(\"{} constraint violations detected in input model\", new java.lang.Object[] { new java.lang.Integer(violations.size()) });\n");

			// now grab the ValidationHandler
			Invocable invocable = (Invocable) method.getAnnotation(Invocable.class);
			preCode.append("\t\t\tif(handler == null) {\n");
			preCode.append("\t\t\t\thandler = new ").append(invocable.validator().getCanonicalName()).append("();\n");
			preCode.append("\t\t\t}\n");
			preCode.append("\t\t\tjava.lang.String result = handler.onModelViolations(").append("\"").append(action).append("\", \"")
					.append(method.getName()).append("\", ").append(i).append(", ").append(Types.getAsString(wrapped))
					.append(".class, violations);\n");
			preCode.append("\t\t\tif(result != null) {\n");
			preCode.append("\t\t\t\tlogger.debug(\"violation handler forced return value to be '{}'\", result);\n");
			preCode.append("\t\t\t\treturn result;\n");
			preCode.append("\t\t\t}\n");
			preCode.append("\t\t}\n");
			preCode.append("\t}\n\n");

			preCode.append("\ttrace.append(\"").append(variable).append("\").append(\" => '\").append(").append(variable)
					.append(".get()).append(\"', \");\n");

			preCode.append("\n");

			//
			// code executed AFTER the action has returned, to store values into
			// scopes
			//
			Scope scope = out.to();
			String parameter = out.value();
			postCode.append("\t//\n\t// storing input/output model argument '").append(parameter).append("' (no. ").append(i).append(", ")
					.append(Types.getAsString(wrapped)).append(") into scope ").append(scope.name()).append("\n\t//\n");
			postCode.append("\tvalue = ").append(variable).append(".get();\n");
			postCode.append("\tif(value != null) {\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.setValue( \"").append(out.value())
					.append("\", value, org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(" );\n");
			postCode.append("\t} else if(").append(variable).append(".isReset()) {\n");
			// postCode.append("\t\torg.dihedron.zephyr.ActionContext.removeValueFromScope( \"").append(parameter).append("\", ").append("org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(" );\n");
			postCode.append("\t\torg.dihedron.zephyr.ActionContext.removeValue( \"").append(parameter).append("\", ")
					.append("org.dihedron.zephyr.protocol.Scope.").append(scope.name()).append(" );\n");
			postCode.append("\t}\n");
			postCode.append("\n");

			return variable;
		}

		private String prepareNonAnnotatedArgument(int i, Class<?> type, StringBuilder code, boolean doValidation) throws DeploymentException {

			code.append("\t//\n\t// preparing non-annotated argument no. ").append(i).append(" (").append(Types.getAsString(type))
					.append(")\n\t//\n");

			logger.warn("{}-th parameter has no @In or @Out annotation!", i);
			if (!type.isPrimitive()) {
				logger.trace("{}-th parameter will be passed in as a null object", i);
				code.append("\t").append(Types.getAsString(type)).append(" arg").append(i).append(" = null;\n");
				code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => null, \");\n");
				if (doValidation) {
					code.append("\t// non annotated object reference parameter\n");
					code.append("\tif(validationValues != null) validationValues.add(null);\n");
				}
			} else {
				logger.trace("{}-th parameter is a primitive type", i);
				if (type == Boolean.TYPE) {
					logger.trace("{}-th parameter will be passed in as a boolean 'false'", i);
					code.append("\tboolean arg").append(i).append(" = false;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => false, \");\n");
					if (doValidation) {
						code.append("\t// non annotated boolean parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Boolean(false));\n");
					}
				} else if (type == Character.TYPE) {
					logger.trace("{}-th parameter will be passed in as a character ' '", i);
					code.append("\tchar arg").append(i).append(" = ' ';\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => ' ', \");\n");
					if (doValidation) {
						code.append("\t// non annotated character parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Character' ');\n");
					}
				} else if (type == Byte.TYPE) {
					logger.trace("{}-th parameter will be passed in as a byte '0'", i);
					code.append("\tbyte arg").append(i).append(" = 0;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => 0, \");\n");
					if (doValidation) {
						code.append("\t// non annotated byte parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Byte(0));\n");
					}
				} else if (type == Short.TYPE) {
					logger.trace("{}-th parameter will be passed in as a short '0'", i);
					code.append("\tshort arg").append(i).append(" = 0;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => 0, \");\n");
					if (doValidation) {
						code.append("\t// non annotated short parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Short(0));\n");
					}
				} else if (type == Integer.TYPE) {
					logger.trace("{}-th parameter will be passed in as an integer '0'", i);
					code.append("\tint arg").append(i).append(" = 0;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => 0, \");\n");
					if (doValidation) {
						code.append("\t// non annotated integer parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Integer(0));\n");
					}
				} else if (type == Long.TYPE) {
					logger.trace("{}-th parameter will be passed in as a long '0'", i);
					code.append("\tlong arg").append(i).append(" = 0;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => 0, \");\n");
					if (doValidation) {
						code.append("\t// non annotated long parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Long(0));\n");
					}
				} else if (type == Float.TYPE) {
					logger.trace("{}-th parameter will be passed in as a float '0.0'", i);
					code.append("\tfloat arg").append(i).append(" = 0.0;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => 0.0, \");\n");
					if (doValidation) {
						code.append("\t// non annotated float parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Float(0.0));\n");
					}
				} else if (type == Double.TYPE) {
					logger.trace("{}-th parameter will be passed in as a float '0.0'", i);
					code.append("\tdouble arg").append(i).append(" = 0.0;\n");
					code.append("\ttrace.append(\"arg").append(i).append("\").append(\" => 0.0, \");\n");
					if (doValidation) {
						code.append("\t// non annotated double parameter\n");
						code.append("\tif(validationValues != null) validationValues.add(new java.lang.Double(0.0));\n");
					}
				}
			}
			code.append("\n");
			return "arg" + i;
		}

		private String getActionAlias() {
			String alias = action.getSimpleName();
			Action annotation = action.getAnnotation(Action.class);
			if (annotation != null && Strings.isValid(annotation.alias())) {
				alias = annotation.alias();
			}
			logger.trace("alias for class '{}' is '{}'", action.getSimpleName(), alias);
			return alias;
		}

		/**
		 * The logger.
		 */
		private final Logger logger = LoggerFactory.getLogger(ActionProxyBuilderContext.class);
	}

	/**
	 * By default JSR-349 validation code generation is disabled.
	 */
	private static final boolean DEFAULT_DO_VALIDATION = false;
	
	/**
	 * Makes up and returns the name of the class that will proxy the action's 
	 * methods through its static methods.
	 * 
	 * @param action
	 *   the action whose proxy's name is to be retrieved.
	 * @return 
	 *   the name of the proxy class.
	 */
	public static String getProxyClassName(Class<?> action) {
		return proxyClassNamePrefix + action.getName() + proxyClassNameSuffix;
	}

	/**
	 * Makes up and returns the name of the static factory method that each
	 * proxy class will implement in order to enable instantiation of new
	 * classes without having to invoke Class.forName("").newInstance().
	 * 
	 * @param action
	 *            the action to create a factory method for.
	 * @return the name of the factory method.
	 */
	public static String getActionFactoryName(Class<?> action) {
		return actionFactoryMethodName;
	}

	/**
	 * Makes up and returns the name of the static method that will proxy the
	 * given action method.
	 * 
	 * @param method
	 *            the method whose proxy's name is being retrieved.
	 * @return the name of the static proxy method.
	 */
	public static String makeStubMethodName(Method method) {
		return stubMethodNamePrefix + method.getName() + stubMethodNameSuffix;
	}

	/**
	 * The Javassist class pool used to create and stored synthetic classes.
	 */
	private ClassPool classpool = null;
	
	/**
	 * Whether the builder shoud emit code to support JSR-349 validation.
	 */
	private boolean doValidation = DEFAULT_DO_VALIDATION;

	/**
	 * Default constructor, initialises the internal Javassist class pool with
	 * the default instance.
	 */
	public ActionProxyBuilder() {
		this(new ClassPool());
	}

	/**
	 * Constructor.
	 * 
	 * @param classpool
	 *   the Javassist class pool to generate AOP classes.
	 */
	public ActionProxyBuilder(ClassPool classpool) {
		this.classpool = classpool;
	}

	/**
	 * Sets the internal builder state so that it generates JSR-249 validation 
	 * code.
	 * 
	 * @return
	 *   the object itself, for method chaining.
	 */
	public ActionProxyBuilder withValidation() {
		this.doValidation = true;
		return this;
	}
	
	/**
	 * Sets the internal builder state so that it does not generate JSR-249 
	 * validation code.
	 * 
	 * @return
	 *   the object itself, for method chaining.
	 */
	public ActionProxyBuilder withoutValidation() {
		this.doValidation = false;
		return this;
	}
	
	/**
	 * Starts the construction of a new ActionProxy around the given action class.
	 * 
	 * @param action
	 *   the action class on which a proxy should be built.
	 * @return
	 *   the context object, on which further processing methods can be called
	 *   via a fluent API.
	 * @throws DeploymentException
	 */
	public ActionProxyBuilderContext build(Class<?> action) throws DeploymentException {
		return this.new ActionProxyBuilderContext(doValidation).on(action);
	}
}
